/*
 * Copyright (c) 2015-2018 Sfkj Science And Technology Co.,Ltd.
 * All Rights Reserved.
 * This software is the confidential and proprietary information of
 * Sfkj Science And Technology Co.,Ltd.
 * ("Confidential Information").You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sfkj.
 */
package com.sfkj.walk.axon.web;

import com.querydsl.core.types.Predicate;
import com.sfkj.utils.ClassUtils;
import com.sfkj.walk.axon.web.dto.CommandDto;
import com.sfkj.walk.axon.web.dto.CommandResultDto;
import lombok.Getter;
import lombok.Setter;
import org.axonframework.queryhandling.QueryGateway;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.querydsl.binding.QuerydslBindings;
import org.springframework.data.querydsl.binding.QuerydslBindingsFactory;
import org.springframework.data.querydsl.binding.QuerydslPredicateBuilder;
import org.springframework.data.util.ClassTypeInformation;
import org.springframework.data.util.TypeInformation;
import org.springframework.data.web.PageableDefault;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.support.WebRequestDataBinder;
import org.springframework.web.context.request.NativeWebRequest;

import java.util.Arrays;
import java.util.Map;
import java.util.Objects;

/**
 * 动态命令调用
 */
public abstract class AbstractQueryGatewayController<C, Q, R> {
    private Class<C> commandType;
    private Class<Q> queryType;
    private Class<R> responseType;
    @Autowired
    private QueryGateway queryGateway;

    @Autowired
    private QuerydslBindingsFactory bindingsFactory;
    @Autowired
    @Qualifier("mvcConversionService")
    ObjectFactory<ConversionService> conversionService;
    private QuerydslPredicateBuilder predicateBuilder;

    @Getter
    @Setter
    protected Predicate predicate;
    @Getter
    @Setter
    protected Pageable pageable;
    @Getter
    @Setter
    protected Sort sort;

    @PostMapping
    public CommandResultDto queryGateway(@RequestBody(required = false) CommandDto<C> commandDto,
                                         @PageableDefault(size = Integer.MAX_VALUE) Pageable pageable,
                                         Sort sort,
                                         NativeWebRequest webRequest) throws IllegalAccessException, InstantiationException {
        Predicate predicate = resolveArgument(webRequest);

        C command = Objects.isNull(commandDto) || Objects.isNull(commandDto.getPayload()) ?
            getCommandType().newInstance() : commandDto.getPayload();

        resolveArgument(webRequest, command);

        AbstractQueryGatewayController requestDto = (AbstractQueryGatewayController) command;
        requestDto.setPredicate(predicate);
        if (Objects.nonNull(webRequest.getParameter("page"))) {
            requestDto.setPageable(pageable);
        }
        if (Objects.nonNull(webRequest.getParameter("sort"))) {
            requestDto.setSort(sort);
        }

        Object result = queryGateway.send(command, getResponseType()).getNow(null);
        CommandResultDto dto = new CommandResultDto();
        dto.setResult(result);
        return dto;
    }

    private Predicate resolveArgument(NativeWebRequest webRequest) {

        MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();

        for (Map.Entry<String, String[]> entry : webRequest.getParameterMap().entrySet()) {
            parameters.put(entry.getKey(), Arrays.asList(entry.getValue()));
        }

        TypeInformation<?> domainType = ClassTypeInformation.from(getQueryType());
        QuerydslBindings bindings = bindingsFactory.createBindingsFor(null, domainType);

        return getPredicateBuilder().getPredicate(domainType, parameters, bindings);
    }

    private void resolveArgument(NativeWebRequest webRequest, C command) throws IllegalAccessException {
        WebRequestDataBinder binder = new WebRequestDataBinder(command);
        binder.bind(webRequest);
    }

    private QuerydslPredicateBuilder getPredicateBuilder() {
        if (Objects.isNull(predicateBuilder)) {
            predicateBuilder = new QuerydslPredicateBuilder(
                conversionService == null ? new DefaultConversionService() : conversionService.getObject(),
                bindingsFactory.getEntityPathResolver());
        }
        return predicateBuilder;
    }

    @SuppressWarnings("unchecked")
    private Class<C> getCommandType() {
        if (Objects.isNull(commandType)) {
            commandType = (Class<C>) ClassUtils.getSuperClassGenricType(getClass(), 0);
        }
        return commandType;
    }

    @SuppressWarnings("unchecked")
    private Class<Q> getQueryType() {
        if (Objects.isNull(queryType)) {
            queryType = (Class<Q>) ClassUtils.getSuperClassGenricType(getClass(), 1);
        }
        return queryType;
    }

    @SuppressWarnings("unchecked")
    private Class<R> getResponseType() {
        if (Objects.isNull(responseType)) {
            responseType = (Class<R>) ClassUtils.getSuperClassGenricType(getClass(), 2);
        }
        return responseType;
    }
}
